import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { AbstractPick, IAbstractPickOptions } from './abstract-pick';
import { Scene } from '@babylonjs/core/scene';
import { Helper } from '../tool/helper';
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { PickingInfo } from '@babylonjs/core/Collisions/pickingInfo';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { TargetCamera } from '@babylonjs/core/Cameras/targetCamera';
import { AnimationFrame } from '../animation/animation-frame';
import { BabylonContainer } from '../base/babylon-container';


/**
 * 还原视角的方式
 * @param SPHERE 球形运动到给定视角
 * @param FLAT 线性运动到给定视角
 * @param CURRENT 线性运动到给定距离的当前视角
 */
enum RECOVER_TYPE {
    SPHERE, FLAT, CURRENT,
}

/**
 * 聚焦参数对象
 * @param recoverOnEmpty 点击空白时还原视角
 * @param lerpSpeed 插值速度
 * @param linearLerp 是否线性插值（速度恒定）
 * @param recoverToFixed 还原到固定视角
 * @param recoverTarget 还原到的目标（焦点）
 * @param recoverPosition 还原到的位置
 * @param recoverActionType 还原视角的方式
 * @param approachWhenFocus 聚焦时是否靠近（拉近视角）
 * @param approachClosest 靠近的最近距离
 * @param approachRatio 靠近到的比例
 * @param useClick 使用单击
 * @param useDoubleClick 使用双击
 */
interface IFocusOnOptions extends IAbstractPickOptions {
    recoverOnEmpty?: boolean,
    lerpSpeed?: number,
    linearLerp?: boolean,
    recoverToFixed?: boolean,
    recoverTarget?: Vector3,
    recoverPosition?: Vector3,
    recoverActionType?: RECOVER_TYPE,
    approachWhenFocus?: boolean,
    approachClosest?: number,
    approachRatio?: number,
    useClick?: boolean,
    useDoubleClick?: boolean,
}


/**
 * 聚焦
 * @param scene 所在的场景
 * @param options 参数对象
 */
class FocusOn extends AbstractPick<TransformNode>{

    private _recoverOnEmpty: boolean;
    private _tv0 = new Vector3();
    private _tv1 = new Vector3();
    private _tv2 = new Vector3();
    private _tv3 = new Vector3();
    private _tv4 = new Vector3();
    private _tmpPos = new Vector3();
    private _tmpTar = new Vector3();
    private _timer = {};
    private _speed = 0.05;
    private _prg = 0;
    private _linear = false;
    private _recoverToFixed: boolean;
    private _recoverPosition = new Vector3();
    private _recoverTarget = new Vector3();
    private _tq0 = new Quaternion();
    private _noRot = Quaternion.Identity();
    private _tq1 = new Quaternion();
    private _rcvTyp: RECOVER_TYPE;
    private _approach: boolean;
    private _closest: number;
    private _approachRatio: number;
    private _oriPosition = new Vector3();
    private _oriTarget = new Vector3();
    private _useClick: boolean;
    private _useDoubleClick: boolean;

    constructor(scene: Scene, options?: IFocusOnOptions) {
        super(scene, options);
        const ops = options || {};
        this._recoverOnEmpty = ops.recoverOnEmpty === undefined ? true : ops.recoverOnEmpty;
        this._speed = ops.lerpSpeed || 0.05;
        this._linear = ops.linearLerp || false;
        this._recoverToFixed = ops.recoverToFixed || false;
        this._recoverPosition.copyFrom(ops.recoverPosition || (scene.activeCamera ? scene.activeCamera.position : BabylonContainer.backward.clone().scaleInPlace(10)));
        this._recoverTarget.copyFrom(ops.recoverTarget || (scene.activeCamera ? (scene.activeCamera as TargetCamera).target : new Vector3(0, 0, 0)));
        this._rcvTyp = ops.recoverActionType || RECOVER_TYPE.CURRENT;
        this._approach = ops.approachWhenFocus === undefined ? true : ops.approachWhenFocus;
        this._closest = ops.approachClosest || 4;
        this._approachRatio = ops.approachRatio === undefined ? 0.5 : ops.approachRatio;
        this._useClick = ops.useClick || false;
        this._useDoubleClick = ops.useDoubleClick === undefined ? true : ops.useDoubleClick;

        this._oriPosition.copyFrom(this._recoverPosition);
        this._oriTarget.copyFrom(this._recoverTarget);
    }

    /**
     * 释放当前聚焦（立即还原）
     * @param recoverPosition 还原到的位置
     * @param recoverTarget 还原到的焦点
     * @param force 是否更新原始位置、焦点为当前值
     */
    public release(recoverPosition?: Vector3, recoverTarget?: Vector3, force?: boolean): void {
        AnimationFrame.cancel(this._timer);
        this._recoverPosition.copyFrom(recoverPosition ? recoverPosition : this._oriPosition);
        this._recoverTarget.copyFrom(recoverTarget ? recoverTarget : this._oriTarget);
        if (!force) return;
        this._oriPosition.copyFrom(this._recoverPosition);
        this._oriTarget.copyFrom(this._recoverTarget);
    }

    /**
     * 视角聚焦
     * @param target 目标焦点
     */
    public focus(target: Vector3): void {
        const camera = this.scene.activeCamera as TargetCamera;
        if (!camera) return;
        this._prg = 0;

        if (!this._recoverToFixed) {
            this._recoverPosition.copyFrom(camera.position);
            this._recoverTarget.copyFrom(camera.target);
        }

        const oTar = this._tv3.copyFrom(camera.target);
        const offset = this._tv1.copyFrom(target).subtractInPlace(oTar);
        const tPos = this._tv0.copyFrom(camera.position).addInPlace(offset);
        const tTar = this._tv2.copyFrom(camera.target).copyFrom(target);
        const oPos = this._tv1.copyFrom(camera.position);

        if (this._approach) {
            const d = this._tv4.copyFrom(oPos).subtractInPlace(oTar);
            const l = Math.max(d.length() * this._approachRatio, this._closest);
            tPos.copyFrom(tTar).addInPlace(d.normalize().scaleInPlace(l));
        }

        AnimationFrame.request((t: number) => {
            const p = this.stepProgress();
            camera.position = Vector3.LerpToRef(oPos, tPos, p, this._tmpPos);
            camera.target = Vector3.LerpToRef(oTar, tTar, p, this._tmpTar);
            return this._prg >= 1;
        }, 16, 200, this._timer);
    }

    /**
     * 还原视角
     */
    public recover(): void {
        const camera = this.scene.activeCamera as TargetCamera;
        if (!camera) return;
        this._prg = 0;

        const tPos = this._recoverPosition;
        const tTar = this._recoverTarget;
        const oPos = this._tv0.copyFrom(camera.position);
        const oTar = this._tv1.copyFrom(camera.target);
        const od = this._tv2.copyFrom(oPos).subtractInPlace(oTar).normalize();
        const td = this._tv3.copyFrom(tPos).subtractInPlace(tTar).normalize();
        const tRot = Quaternion.FromUnitVectorsToRef(od, td, this._tq0);
        const oRot = this._noRot;
        const ol = Vector3.Distance(oPos, oTar);
        const tl = Vector3.Distance(tPos, tTar);

        AnimationFrame.request((t: Number) => {
            const p = this.stepProgress();
            camera.target = Vector3.LerpToRef(oTar, tTar, p, this._tmpTar);

            const l = (tl - ol) * p + ol;
            const pos = this._tmpPos;
            switch (this._rcvTyp) {
                case RECOVER_TYPE.SPHERE:
                    const r = Quaternion.SlerpToRef(oRot, tRot, p, this._tq1);
                    const d = this._tv4.copyFrom(od).applyRotationQuaternionInPlace(r);
                    pos.copyFrom(camera.target).addInPlace(d.scaleInPlace(l));
                    break;
                case RECOVER_TYPE.FLAT:
                    Vector3.LerpToRef(oPos, tPos, p, pos);
                    break;
                case RECOVER_TYPE.CURRENT:
                    const dir = this._tv4.copyFrom(od);
                    pos.copyFrom(camera.target).addInPlace(dir.scaleInPlace(l));
                    break;
            }
            camera.position = pos;

            return this._prg >= 1;
        }, 16, 200, this._timer);
    }

    /**
     * 销毁（并释放聚焦）
     */
    public dispose(): void {
        this.release();
        super.dispose();
    }

    protected compare(a: TransformNode, b: TransformNode): boolean {
        return a.id === b.id;
    }

    protected getMeshesFromTarget(target: TransformNode): AbstractMesh[] {
        return Helper.getChildMeshes(target);
    }

    protected clickHandle(pickInfo: PickingInfo | null): void {
        if (!this._useClick) return;
        this.focusHandle(pickInfo);
    }

    protected doubleClickHandle(pickInfo: PickingInfo | null): void {
        if (!this._useDoubleClick) return;
        this.focusHandle(pickInfo);
    }

    /**
     * 聚焦处理函数
     * @param pickInfo 拾取信息
     */
    private focusHandle(pickInfo: PickingInfo | null): void {
        if (!pickInfo) {
            if (this._recoverOnEmpty) this.recover();
            return;
        }
        if (!pickInfo.pickedPoint) {
            if (this._recoverOnEmpty) this.recover();
            return;
        }
        this.focus(pickInfo.pickedPoint);
    }

    /**
     * 步进动画进程
     * @returns 处理后的进程值
     */
    private stepProgress(): number {
        if (this._linear) {
            this._prg += this._speed;
        } else {
            let s = (0.5 - Math.abs(0.5 - this._prg)) * 3;
            s = s * this._speed + this._speed * 0.2;
            this._prg += s;
        }
        return Math.min(this._prg, 1);
    }

}


export { FocusOn, RECOVER_TYPE };
export type { IFocusOnOptions };